package org.example.system.filter;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.JWTPayload;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.system.common.constant.SecurityConstants;
import org.example.system.common.exception.BusinessException;
import org.example.system.common.result.ResultCode;
import org.example.system.common.util.ResponseUtils;
import org.example.system.security.util.JwtUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Map;

/**
 * JWT token 校验过滤器
 *
 * @author LY
 */
public class JwtValidationFilter extends OncePerRequestFilter {

    private final RedisTemplate<String, Object> redisTemplate;

    public JwtValidationFilter(RedisTemplate<String, Object> redisTemplate) {
        if (redisTemplate == null) {
            throw new IllegalArgumentException("RedisTemplate must not be null");
        }
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String token = request.getHeader(HttpHeaders.AUTHORIZATION);
            if (StrUtil.isNotBlank(token)) {
                validateToken(token, response);
            }
            filterChain.doFilter(request, response);
        } catch (BusinessException ex) {
            handleAuthenticationFailure(response, ex);
        }
    }

    private void validateToken(String token, HttpServletResponse response) throws BusinessException {
        Map<String, Object> payload = JwtUtils.parseToken(token);
        String jti = extractJtiFromPayload(payload);

        if (isTokenBlacklisted(jti)) {
            throw new BusinessException(ResultCode.TOKEN_INVALID);
        }

        setAuthentication(payload);
    }

    private String extractJtiFromPayload(Map<String, Object> payload) {
        return payload != null ? Convert.toStr(payload.get(JWTPayload.JWT_ID)) : null;
    }

    private boolean isTokenBlacklisted(String jti) {
        return jti != null && Boolean.TRUE.equals(
            redisTemplate.hasKey(SecurityConstants.BLACKLIST_TOKEN_PREFIX + jti)
        );
    }

    private void setAuthentication(Map<String, Object> payload) {
        Authentication authentication = JwtUtils.getAuthentication(payload);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    private void handleAuthenticationFailure(HttpServletResponse response, BusinessException ex) {
        SecurityContextHolder.clearContext();
        try {
            ResponseUtils.writeErrMsg(response, (ResultCode) ex.getResultCode());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

